use std::fmt::Display;

use crate::{
    char_seq::Pos,
    item::{
        Call, Enum, Expr, ExprPos, Func, IfBranch, IfExpr, Let, Stat, StatPos, Struct, Switch,
        Type, Union, VarDeclC,
    },
    parser::MC_MAIN_FUNC,
};
use convert_case::{Case, Casing};

pub struct StatPosC<'a> {
    stat_pos: &'a StatPos,
    gen_line: bool,
}

impl<'a> StatPosC<'a> {
    pub fn new(stat_pos: &'a StatPos, gen_line: bool) -> Self {
        Self { stat_pos, gen_line }
    }
}

impl<'a> Display for StatPosC<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Stat::Import(_) = self.stat_pos.stat {
            return Ok(());
        }

        write!(
            f,
            "{}",
            StatIndent {
                stat: &self.stat_pos,
                indent: 0,
                gen_line: self.gen_line,
            }
        )?;
        Ok(())
    }
}

pub struct CompimeConstant<'a> {
    stat: &'a Stat,
}

impl<'a> CompimeConstant<'a> {
    pub fn new(stat: &'a Stat) -> Self {
        Self { stat }
    }
}

impl<'a> Display for CompimeConstant<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.stat {
            Stat::ComptimeConstant {
                decl,
                expr,
                expr_display,
            } => {
                writeln!(f, "\n/* {name} = {expr_display} */", name = decl.name,)?;

                writeln!(f, "enum {{")?;
                writeln!(
                    f,
                    "\t_CONST_{name} = {expr}",
                    name = decl.name,
                    expr = ExprC::new(&expr.expr, 0)
                )?;
                writeln!(f, "}};")?;
                Ok(())
            }
            _ => Ok(()),
        }
    }
}

pub struct FuncDeclare<'a> {
    stat: &'a StatPos,
    indent: usize,
}

impl<'a> FuncDeclare<'a> {
    pub fn new(stat: &'a StatPos, indent: usize) -> Self {
        Self { stat, indent }
    }
}

impl<'a> Display for FuncDeclare<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.stat.stat {
            Stat::Func(func) => {
                writeln!(f)?;
                write_comment(&self.stat.comments, 0, f)?;
                write_func_declare(func, f, self.indent)?;
                writeln!(f, ";")
            }
            _ => Ok(()),
        }
    }
}

pub struct CustomTypeDefinition<'a> {
    stat: &'a StatPos,
}

impl<'a> CustomTypeDefinition<'a> {
    pub fn new(stat: &'a StatPos) -> Self {
        Self { stat }
    }
}

impl<'a> Display for CustomTypeDefinition<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.stat.stat {
            Stat::Enum(Enum { name, variants }) => {
                writeln!(f)?;
                write_comment(&self.stat.comments, 0, f)?;

                writeln!(f, "enum {name} {{")?;
                for v_with_comment in variants {
                    write_comment(&v_with_comment.comment, 1, f)?;

                    write_indent(1, f)?;
                    writeln!(f, "{}_{},", name, v_with_comment.inner)?;
                }
                writeln!(f, "}};")?;
                Ok(())
            }
            Stat::Struct(c) => {
                writeln!(f)?;
                write_comment(&self.stat.comments, 0, f)?;

                writeln!(f, "struct {} {{", c.name)?;

                for field in &c.fields {
                    write_indent(1, f)?;
                    writeln!(f, "{};", VarDeclC::new(&field.inner))?;
                }

                writeln!(f, "}};")?;
                Ok(())
            }
            Stat::Union(c) => {
                writeln!(f)?;
                write_comment(&self.stat.comments, 0, f)?;

                writeln!(f, "union {} {{", c.name)?;

                for field in &c.variants {
                    write_indent(1, f)?;
                    writeln!(f, "{} {};", field.inner.ty, field.inner.name)?;
                }

                writeln!(f, "}};")?;
                Ok(())
            }
            _ => Ok(()),
        }
    }
}

pub struct CustomTypeDeclaration<'a> {
    stat: &'a Stat,
}

impl<'a> CustomTypeDeclaration<'a> {
    pub fn new(stat: &'a Stat) -> Self {
        Self { stat }
    }
}

impl<'a> Display for CustomTypeDeclaration<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.stat {
            Stat::Enum(Enum { name, .. }) => {
                writeln!(f, "\ntypedef enum {name} {name};")?;
                Ok(())
            }
            Stat::Struct(Struct { name, .. }) => {
                writeln!(f, "\ntypedef struct {name} {name};")?;
                Ok(())
            }
            Stat::Union(Union { name, .. }) => {
                writeln!(f, "\ntypedef union {name} {name};")?;
                Ok(())
            }
            _ => Ok(()),
        }
    }
}

struct StatIndent<'a> {
    indent: usize,
    stat: &'a StatPos,
    gen_line: bool,
}

impl<'a> Display for StatIndent<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if !matches!(self.stat.stat, Stat::Func(_)) {
            write_comment(&self.stat.comments, self.indent, f)?;
        }

        if self.gen_line {
            write_indent(self.indent, f)?;
            writeln!(f, "#line {} \"{}\"", self.stat.pos.row, self.stat.pos.file)?;
        }

        write_indent(self.indent, f)?;

        match &self.stat.stat {
            Stat::ComptimeConstant { .. } => Ok(()),
            Stat::Import(_) => Ok(()),
            Stat::Struct(_) => Ok(()),
            Stat::Enum(_) => Ok(()),
            Stat::Union(_) => Ok(()),
            Stat::Return(expr) => match &expr {
                Some(expr) => {
                    writeln!(
                        f,
                        "return {};",
                        ExprC::without_parentheses(&expr.expr, self.indent)
                    )
                }
                None => {
                    writeln!(f, "return;")
                }
            },
            Stat::Expr(expr) => writeln!(f, "{};", ExprC::new(&expr.expr, self.indent)),
            Stat::Let(let_stat) => write_let(f, self.indent, let_stat),
            Stat::LetUninit(let_stat) => {
                writeln!(f, "{decl};", decl = VarDeclC::new(let_stat))
            }
            Stat::Func(func) => write_func(f, self.indent + 1, func, self.gen_line),
            Stat::Switch(Switch {
                head,
                branch,
                default,
                has_default,
            }) => {
                writeln!(
                    f,
                    "switch ({}) {{",
                    ExprC::without_parentheses(&head.expr, self.indent)
                )?;

                let head_ty = match head.ty.clone() {
                    Type::Const(inner) => *inner,
                    x => x,
                };

                let name = match &head_ty {
                    Type::Ref { name, .. } => name,
                    _ => unreachable!("head of switch is not enum: {}", head_ty),
                };

                for b in branch {
                    write_indent(self.indent + 1, f)?;
                    writeln!(f, "case {}_{}: {{", name, b.variant)?;

                    for stat in &b.stat {
                        write!(
                            f,
                            "{}",
                            StatIndent {
                                indent: self.indent + 2,
                                stat: &stat,
                                gen_line: self.gen_line,
                            }
                        )?;
                    }

                    write_indent(self.indent + 2, f)?;
                    writeln!(f, "break;")?;

                    write_indent(self.indent + 1, f)?;
                    writeln!(f, "}}")?;
                }

                if !has_default {
                    write_indent(self.indent + 1, f)?;
                    writeln!(f, "default: {{")?;

                    write!(
                        f,
                        "{}",
                        StatIndent {
                            indent: self.indent + 2,
                            stat: &StatPos {
                                comments: vec![],
                                stat: Stat::Expr(ExprPos {
                                    expr: Expr::Call(Call {
                                        is_macro: false,
                                        is_method: false,
                                        func: "abort".into(),
                                        args: vec![]
                                    }),
                                    pos: Pos::zero(),
                                    ty: Type::Unknown,
                                }),
                                pos: Pos::zero(),
                            },
                            gen_line: self.gen_line,
                        },
                    )?;

                    write_indent(self.indent + 1, f)?;
                    writeln!(f, "}}")?;
                } else {
                    write_indent(self.indent + 1, f)?;
                    writeln!(f, "default: {{")?;

                    for stat in default {
                        write!(
                            f,
                            "{}",
                            StatIndent {
                                indent: self.indent + 2,
                                stat: &stat,
                                gen_line: self.gen_line,
                            }
                        )?;
                    }

                    write_indent(self.indent + 1, f)?;
                    writeln!(f, "}}")?;
                }

                write_indent(self.indent, f)?;
                writeln!(f, "}}")?;
                Ok(())
            }
            Stat::For {
                index,
                index_pos: _,
                iterator,
                body,
                index_ty,
            } => {
                writeln!(
                    f,
                    "for ({index_ty} {index} = 0; {index} < {upper}; ++{index}) {{",
                    upper = ExprC::without_parentheses(&iterator.expr, self.indent),
                )?;

                for stat in body {
                    write!(
                        f,
                        "{}",
                        StatIndent {
                            indent: self.indent + 1,
                            stat,
                            gen_line: self.gen_line
                        }
                    )?;
                }
                write_indent(self.indent, f)?;
                writeln!(f, "}}")?;
                Ok(())
            }
            Stat::While { cond, body } => {
                writeln!(
                    f,
                    "while ({}) {{",
                    ExprC::without_parentheses(&cond.expr, self.indent)
                )?;

                for stat in body {
                    write!(
                        f,
                        "{}",
                        StatIndent {
                            indent: self.indent + 1,
                            stat: &stat,
                            gen_line: self.gen_line
                        }
                    )?;
                }

                write_indent(self.indent, f)?;
                writeln!(f, "}}")?;
                Ok(())
            }
            Stat::Break => {
                writeln!(f, "break;")
            }
            Stat::Continue => {
                writeln!(f, "continue;")
            }
            Stat::If {
                head,
                elif,
                otherwise,
            } => {
                write_if(self.indent, self.gen_line, f, head)?;

                for br in elif {
                    write!(f, " else ")?;
                    write_if(self.indent, self.gen_line, f, br)?;
                }

                if !otherwise.is_empty() {
                    writeln!(f, " else {{")?;

                    for stat in otherwise {
                        write!(
                            f,
                            "{}",
                            StatIndent {
                                stat: &stat,
                                indent: self.indent + 1,
                                gen_line: self.gen_line,
                            }
                        )?;
                    }

                    write_indent(self.indent, f)?;
                    write!(f, "}}")?;
                }

                writeln!(f)?;

                Ok(())
            }
            Stat::Assign { target, value } => writeln!(
                f,
                "{} = {};",
                ExprC::without_parentheses(&target.expr, self.indent),
                ExprC::without_parentheses(&value.expr, self.indent)
            ),
            Stat::ComputeAssign { op, target, value } => writeln!(
                f,
                "{} {op}= {};",
                ExprC::without_parentheses(&target.expr, self.indent),
                ExprC::without_parentheses(&value.expr, self.indent)
            ),
        }
    }
}

// /* FIRST LINE
// SECOND LINE
// ...
// LAST LINE */
fn write_comment(
    comments: &[String],
    indent: usize,
    f: &mut std::fmt::Formatter,
) -> Result<(), std::fmt::Error> {
    if comments.is_empty() {
        return Ok(());
    }

    write_indent(indent, f)?;
    write!(f, "/* ")?;

    let mut first = true;
    for line in comments {
        let line = line.trim();

        if first {
            first = false;
            write!(f, "{}", line)?;
        } else {
            writeln!(f)?;
            write_indent(indent, f)?;
            write!(f, "{}", line)?;
        }
    }

    writeln!(f, " */")?;

    Ok(())
}

pub struct BareNumberC<'e> {
    expr: &'e Expr,
}

impl<'e> BareNumberC<'e> {
    pub fn new(expr: &'e Expr) -> Self {
        Self { expr }
    }
}

impl<'e> Display for BareNumberC<'e> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.expr {
            Expr::Int(n) => write!(f, "{n}"),
            Expr::UInt(n) => write!(f, "{n}"),
            Expr::I32(n) => write!(f, "{n}"),
            Expr::U32(n) => write!(f, "{n}"),
            Expr::I64(n) => write!(f, "{n}"),
            Expr::U64(n) => write!(f, "{n}"),
            Expr::Variable(n) => write!(f, "_CONST_{n}"),
            _ => unreachable!("{:?}", self.expr),
        }
    }
}

pub struct ExprC<'e> {
    expr: &'e Expr,
    indent: usize,
    need_parentheses: bool,
}

impl<'e> ExprC<'e> {
    pub fn new(expr: &'e Expr, indent: usize) -> Self {
        Self {
            expr,
            indent,
            need_parentheses: true,
        }
    }

    fn without_parentheses(expr: &'e Expr, indent: usize) -> Self {
        Self {
            expr,
            indent,
            need_parentheses: false,
        }
    }

    fn open_parentheses(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.need_parentheses {
            write!(f, "(")?;
        }
        Ok(())
    }

    fn close_parentheses(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.need_parentheses {
            write!(f, ")")?;
        }
        Ok(())
    }

    fn new_exprc<'this, 'that>(&'this self, expr: &'that Expr) -> ExprC<'that> {
        ExprC::new(expr, self.indent)
    }

    fn new_exprc_without_parentheses<'this, 'that>(&'this self, expr: &'that Expr) -> ExprC<'that> {
        ExprC::without_parentheses(expr, self.indent)
    }
}

impl<'e> Display for ExprC<'e> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.expr {
            Expr::Get { expr, name } => {
                write!(f, "{}.{}", self.new_exprc(&expr.expr), name)
            }
            Expr::EnumGet { name, variant } => write!(f, "{name}_{variant}"),
            Expr::Take { expr, index } => {
                write!(
                    f,
                    "{}[{}]",
                    self.new_exprc(&expr.expr),
                    self.new_exprc_without_parentheses(&index.expr)
                )
            }

            Expr::Null => write!(f, "NULL"),

            // The type of the integer constant can reach to `long long int`
            // and `unsigned long long int` without suffix.
            Expr::Int(i) => write!(f, "isize_({i})"),
            Expr::UInt(i) => write!(f, "usize_({i})"),

            Expr::I32(i) => write!(f, "i32_({i})"),
            Expr::U32(i) => write!(f, "u32_({i})"),
            Expr::I64(i) => write!(f, "i64_({i})"),
            Expr::U64(i) => write!(f, "u64_({i})"),

            Expr::String(s) => write!(f, "{s:?}"),
            Expr::Boolean(b) => write!(f, "{b}"),
            Expr::Char(c) => write!(f, "{c:?}"),
            Expr::Variable(a) => {
                if a.is_case(Case::UpperSnake) {
                    write!(f, "_CONST_{a}")
                } else {
                    write!(f, "{a}")
                }
            }
            Expr::Cast { expr, ty } => {
                self.open_parentheses(f)?;
                write!(f, "({}){}", ty, self.new_exprc(&expr.expr))?;
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::Compute { op, head, rest } => {
                self.open_parentheses(f)?;

                write!(f, "{}", self.new_exprc(&head.expr))?;
                for x in rest {
                    write!(f, " {op} {}", self.new_exprc(&x.expr))?;
                }
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::Not { cond } => {
                self.open_parentheses(f)?;
                write!(f, "!{}", self.new_exprc(&cond.expr))?;
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::And { head, rest } => {
                self.open_parentheses(f)?;

                write!(f, "{}", self.new_exprc(&head.expr))?;
                for x in rest {
                    write!(f, " && {}", self.new_exprc(&x.expr))?;
                }

                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::Or { head, rest } => {
                self.open_parentheses(f)?;

                write!(f, "{}", self.new_exprc(&head.expr))?;
                for x in rest {
                    write!(f, " || {}", self.new_exprc(&x.expr))?;
                }

                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::BShl { expr, length } => {
                self.open_parentheses(f)?;

                write!(
                    f,
                    "{} << {}",
                    self.new_exprc(&expr.expr),
                    self.new_exprc(&length.expr)
                )?;

                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::BShr { expr, length } => {
                self.open_parentheses(f)?;

                write!(
                    f,
                    "{} >> {}",
                    self.new_exprc(&expr.expr),
                    self.new_exprc(&length.expr)
                )?;

                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::Compare { cmp, lhs, rhs } => {
                self.open_parentheses(f)?;
                write!(
                    f,
                    "{} {cmp} {}",
                    self.new_exprc(&lhs.expr),
                    self.new_exprc(&rhs.expr)
                )?;
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::Call(call) => write_call(f, self.indent, call),
            Expr::Dref(expr) => {
                self.open_parentheses(f)?;
                write!(f, "*{}", self.new_exprc(&expr.expr))?;
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::IfExpr(IfExpr {
                branch,
                otherwise: default,
            }) => {
                self.open_parentheses(f)?;
                for (cond, result) in branch {
                    write!(
                        f,
                        "({}) ? ({}) : ",
                        self.new_exprc_without_parentheses(&cond.expr),
                        self.new_exprc_without_parentheses(&result.expr)
                    )?;
                }
                write!(f, "({})", self.new_exprc_without_parentheses(&default.expr))?;
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::TakeAddr(expr) => {
                self.open_parentheses(f)?;
                write!(f, "&{}", self.new_exprc(&expr.expr))?;
                self.close_parentheses(f)?;
                Ok(())
            }
            Expr::Sizeof { ty } => {
                write!(f, "sizeof({})", ty)?;
                Ok(())
            }
        }
    }
}

fn write_call(
    f: &mut std::fmt::Formatter<'_>,
    indent: usize,
    call: &Call,
) -> Result<(), std::fmt::Error> {
    if call.is_macro {
        match call.func.as_ref() {
            "file" => write!(f, "__FILE__")?,
            "line" => write!(f, "__LINE__")?,
            "print" => {
                write_print_func(f, call, indent, "print", "stdout", false)?;
            }
            "eprint" => {
                write_print_func(f, call, indent, "eprint", "stderr", false)?;
            }
            "println" => {
                write_print_func(f, call, indent, "println", "stdout", true)?;
            }
            "eprintln" => {
                write_print_func(f, call, indent, "eprintln", "stderr", true)?;
            }
            _ => panic!("undefined macro {func}", func = call.func),
        }
        return Ok(());
    }

    write!(f, "{}(", call.func)?;

    let mut first = true;
    for arg in &call.args {
        if first {
            first = false;
        } else {
            write!(f, ", ")?;
        }

        write!(f, "{}", ExprC::without_parentheses(&arg.expr, indent))?;
    }
    write!(f, ")")?;
    Ok(())
}

fn write_print_func(
    f: &mut std::fmt::Formatter,
    call: &Call,
    indent: usize,
    func: &'static str,
    fp: &'static str,
    newline: bool,
) -> Result<(), std::fmt::Error> {
    // Write original macro call to comment.
    write!(f, "/* {func}!")?;
    for arg in &call.args {
        write!(f, "  {}", ExprC::new(&arg.expr, indent))?;
    }
    writeln!(f, " */")?;

    // Indent of first function.
    write_indent(indent, f)?;

    let mut first = true;

    for arg in &call.args {
        if first {
            first = false;
        } else {
            writeln!(f, ";")?;
            write_indent(indent, f)?;
        }

        write_print_item(&arg.expr, &arg.ty, fp, indent, f)?;
    }

    if newline && !first {
        writeln!(f, ";")?;
        write_indent(indent, f)?;

        write_print_item(
            &Expr::String("\n".into()),
            &Type::Char.to_const().to_pointer(),
            fp,
            indent,
            f,
        )?;
    }

    Ok(())
}

fn write_print_item(
    arg: &Expr,
    ty: &Type,
    fp: &'static str,
    indent: usize,
    f: &mut std::fmt::Formatter,
) -> Result<(), std::fmt::Error> {
    match ty {
        Type::Unknown => write!(f, "fputs(\"<unknown>\", {fp})")?,
        Type::Void => write!(f, "fputs(\"<void>\", {fp})")?,
        Type::Char => write!(f, "fputc({}, {fp})", ExprC::new(arg, indent))?,
        Type::Int => write!(
            f,
            "fprintf({fp}, \"%\" PRIiPTR, {})",
            ExprC::new(arg, indent)
        )?,
        Type::UInt => write!(
            f,
            "fprintf({fp}, \"%\" PRIuPTR, {})",
            ExprC::new(arg, indent)
        )?,
        // TODO: use makec generated output function.
        Type::Int32 => write!(
            f,
            "fprintf({fp}, \"%\" PRIi32, {})",
            ExprC::new(arg, indent)
        )?,
        Type::Uint32 => write!(
            f,
            "fprintf({fp}, \"%\" PRIu32, {})",
            ExprC::new(arg, indent)
        )?,
        Type::Int64 => write!(
            f,
            "fprintf({fp}, \"%\" PRIi64, {})",
            ExprC::new(arg, indent)
        )?,
        Type::Uint64 => write!(
            f,
            "fprintf({fp}, \"%\" PRIu64, {})",
            ExprC::new(arg, indent)
        )?,
        Type::Boolean => write!(
            f,
            "fputs({} ? \"true\" : \"false\", {fp})",
            ExprC::new(arg, indent)
        )?,
        Type::Null => {
            write!(f, "fputs(\"(null)\", {fp})")?;
        }
        Type::OptionalPointer(inner) | Type::Pointer(inner) => {
            if let Type::Const(inner_inner) = &**inner {
                if let Type::Char = &**inner_inner {
                    write!(f, "fputs({}, {fp})", ExprC::new(arg, indent))?
                } else {
                    write!(f, "fputs(\"<pointer to const but not char>\", {fp})")?
                }
            } else {
                if let Type::Char = &**inner {
                    write!(f, "fputs({}, {fp})", ExprC::new(arg, indent))?
                } else {
                    write!(f, "fputs(\"<pointer to not const>\", {fp})")?
                }
            }
        }
        Type::Const(inner) => write_print_item(arg, inner, fp, indent, f)?,
        Type::Array {
            element_type,
            length,
        } => write!(
            f,
            "fputs(\"<array of {}, with length {}>\", {fp}",
            element_type,
            ExprC::new(&length.expr, 0),
        )?,
        Type::Func { return_type, paras } => write!(
            f,
            "fputs(\"<func return {}, with {} parameters>\", {fp}",
            return_type,
            paras.len(),
        )?,
        Type::Ref { name } => write!(
            f,
            // TODO(plus7wist): use a better way to support print custom type
            "display_{fp}_{}({})",
            name.to_case(Case::Snake),
            ExprC::new(arg, indent)
        )?,
    }

    Ok(())
}

fn write_func(
    f: &mut std::fmt::Formatter<'_>,
    indent: usize,
    func: &Func,
    gen_line: bool,
) -> Result<(), std::fmt::Error> {
    writeln!(f)?;

    write_func_declare(func, f, indent)?;

    writeln!(f, "\n{{")?;
    for stat in &func.body {
        write!(
            f,
            "{}",
            StatIndent {
                stat: &stat,
                indent,
                gen_line,
            }
        )?;
    }

    writeln!(f, "}}")?;

    if func.name == MC_MAIN_FUNC {
        writeln!(f, "\nint main(void)")?;
        writeln!(f, "{{\n")?;
        writeln!(f, "\t{}();", MC_MAIN_FUNC)?;
        writeln!(f, "\treturn 0;")?;
        writeln!(f, "}}")?;
    }
    Ok(())
}

fn write_func_declare(
    func: &Func,
    f: &mut std::fmt::Formatter<'_>,
    indent: usize,
) -> Result<(), std::fmt::Error> {
    if !func.public {
        write!(f, "static ")?;
    }

    write!(f, "{} {}(", func.return_type, func.name)?;
    if func.paras.is_empty() {
        write!(f, "void")?;
    }

    let break_args = func.paras.len() >= 3;

    for (i, arg) in func.paras.iter().enumerate() {
        if break_args {
            writeln!(f)?;
            write_indent(indent, f)?;
        }

        write!(f, "{}", VarDeclC::new(&arg))?;
        if i + 1 != func.paras.len() {
            if break_args {
                write!(f, ",")?;
            } else {
                write!(f, ", ")?;
            }
        }
    }

    write!(f, ")")?;
    Ok(())
}

fn write_indent(indent: usize, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
    for _ in 0..indent {
        write!(f, "\t")?;
    }
    Ok(())
}

fn write_let(
    f: &mut std::fmt::Formatter<'_>,
    indent: usize,
    let_stat: &Let,
) -> Result<(), std::fmt::Error> {
    writeln!(
        f,
        "{} = {};",
        VarDeclC::new(&let_stat.decl),
        ExprC::new(&let_stat.expr.expr, indent)
    )
}

fn write_if(
    indent: usize,
    gen_line: bool,
    f: &mut std::fmt::Formatter<'_>,
    branch: &IfBranch,
) -> Result<(), std::fmt::Error> {
    writeln!(
        f,
        "if ({}) {{",
        ExprC::without_parentheses(&branch.cond.expr, 0)
    )?;

    for stat in &branch.then {
        write!(
            f,
            "{}",
            StatIndent {
                stat: &stat,
                indent: indent + 1,
                gen_line,
            }
        )?;
    }

    write_indent(indent, f)?;
    write!(f, "}}")?;

    Ok(())
}
